
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Collections;
using System.Diagnostics;
using System.Drawing;



namespace Swaf.Gui
{
	public enum RowColType {colType = 0, rowType = 1};
	/// <summary>
	/// Summary description for SpreadsheetLayout.
	/// </summary>
	public class SpreadsheetLayoutAlg
	{
		protected CreateRowCol m_rowColFactory;
		protected Rect m_dimension;
		protected IList m_elements;
		protected IList[] m_rowCols;
		protected IList m_regions;

		protected NumericPair m_defaultCellSize;
		protected NumericPair[] m_defaultMargins;

		public delegate RowCol CreateRowCol(int pos);

		public SpreadsheetLayoutAlg(CreateRowCol rowColFactory)
		{
			m_rowColFactory = rowColFactory;
			initLayout();
		}

		public SpreadsheetLayoutAlg()
		{
			m_rowColFactory = new CreateRowCol(defaultRowColFactory);
			initLayout();
		}

		private void initLayout()
		{
			m_dimension = new Rect(0,0,0,0);
			m_elements = new ArrayList();
			m_defaultCellSize = new NumericPair();

			m_rowCols = new IList[2];
			m_rowCols[0] = new ArrayList();
			m_rowCols[1] = new ArrayList();

			m_defaultMargins = new NumericPair[2];
			m_defaultMargins[0] = new NumericPair();
			m_defaultMargins[1] = new NumericPair();

			m_regions = new ArrayList();
		}

		public void addCell(Cell cell)
		{
			findRowCol(m_rowCols[(int)RowColType.colType], (int)cell.SheetRect.Position.Num1, true).addCell(cell);
			findRowCol(m_rowCols[(int)RowColType.rowType], (int)cell.SheetRect.Position.Num2, true).addCell(cell);
			m_elements.Add(cell);
		}

		public void addRegion(CellRegion reg)
		{
			m_regions.Add(reg);
		}

		protected CellRegion getRegionForCell(Cell cell)
		{
			CellRegion cellReg = null;

			foreach(CellRegion reg in m_regions)
			{
				if(reg.CellArea.contains(cell.SheetRect.Position))
				{
					cellReg = reg;
					break;
				}
			}

			return cellReg;
		}

		protected void addCellToRow(int row, Cell cell)
		{
			findRowCol(m_rowCols[(int)RowColType.rowType], row, true).addCell(cell);
			m_elements.Add(cell);
		}

		protected void addCellToCol(int col, Cell cell)
		{
			findRowCol(m_rowCols[(int)RowColType.colType], col, true).addCell(cell);
			m_elements.Add(cell);
		}

		protected void removeCell(Cell cell)
		{
			m_elements.Remove(cell);
			//TODO: Remove cell from its row and col.
		}

		void removeAll()
		{
			m_elements.Clear();
			m_rowCols[(int)RowColType.rowType].Clear();
			m_rowCols[(int)RowColType.colType].Clear();
		}

		public void recalc(Rect area)
		{
			resetRowColSizes();
			calculateRowColSizes(area, false);
			calculateCellAreas(area);
		}

		public RowCol getRowCol(RowColType type, int position)
		{
			return findRowCol(m_rowCols[(int)type], position, true);
		}

		public IList DefinedCells
		{
			get{return m_elements;}
		}

		public IList definedRowCols(RowColType type)
		{
			return m_rowCols[(int)type]; 
		}

		public NumericPair getRowColSizePos(RowColType type, int position)
		{
			NumericPair num = new NumericPair();
			num.Num1 = pixelSpan(type, 1, position -1);
			num.Num2 = pixelSpan(type, 1, position) - num.Num1;
			return num;
		}

		public NumericPair MinSize	
		{
			get
			{
				if(m_elements.Count == 0)
					return new NumericPair(0,0);

				resetRowColSizes();
				Rect area = new Rect();
				calculateRowColSizes(area, true);
				calculateCellAreas(area);
				return new NumericPair(
					pixelSpan(RowColType.colType, 1, 
						((RowCol)m_rowCols[(int)RowColType.colType][m_rowCols[(int)RowColType.colType].Count -1]).SheetPos),
					pixelSpan(RowColType.rowType, 1,
						((RowCol)m_rowCols[(int)RowColType.rowType][m_rowCols[(int)RowColType.rowType].Count -1]).SheetPos));
			}
		}

		public void setDefaultCell(NumericPair minSize)
		{
			m_defaultCellSize = minSize;
		}

		public void setDefaultMargins(RowColType type, NumericPair margins)
		{
			m_defaultMargins[(int)type] = margins;
		}

		public NumericPair getMargins(RowCol arc, RowColType rowCol)
		{
			return new NumericPair();
		}

		public RowCol findRowCol(IList rcList, int position, bool createIfNotFound)
		{
			foreach(RowCol arc in rcList)
				if(arc.SheetPos == position)
					return arc;

			if(createIfNotFound)
			{
				RowCol arc = m_rowColFactory(position);
				insertRowCol(rcList, arc);
				return arc;
			}

			return null;
		}

		public void insertRowCol(IList rclist, RowCol arc)
		{
			bool elementAdded = false;
			if(rclist.Count == 0 || ((RowCol)rclist[rclist.Count-1]).SheetPos < arc.SheetPos)
			{
				rclist.Add(arc);
				elementAdded = true;
			}
			else
			{
				for(int p = 0; p < rclist.Count; ++p)
				{
					if(((RowCol)rclist[p]).SheetPos >= arc.SheetPos)
					{
						rclist.Insert(p, arc);
						elementAdded = true;
						break;
					}
				}
			}
			if(!elementAdded)
				throw new Exception("Unable to add rowcol");
		}

		public Rect dimensions {get{return m_dimension;}}

		protected void resetRowColSizes()
		{
			for(int rowCol = 0; rowCol <= 1; ++rowCol)
			{
				foreach(RowCol arc in m_rowCols[rowCol])
					arc.resetSize(m_defaultCellSize[rowCol]);
			}
		}

		protected void calculateRowColSizes(Rect area, bool forMinSize)
		{
			for(int rowCol = 0; rowCol <= 1; ++rowCol)
				foreach(RowCol arc in m_rowCols[rowCol])
					foreach(Cell cell in arc.Elements)
					{
						Debug.Assert(cell.SheetRect != null);
						Debug.Assert(cell.MinSize != null);
						if(cell.SheetRect.Size[rowCol] == 1 && cell.MinSize[rowCol] > arc.RowColSize)
							arc.RowColSize = cell.MinSize[rowCol];
					}

			//Next, recalculate row and column sizes based on elements that span more
			//than one col/row.
			for(int rowCol = 0; rowCol <= 1; ++rowCol)
				foreach(RowCol arc in m_rowCols[rowCol])
					foreach(Cell cell in arc.Elements)
						if(cell.SheetRect.Size[rowCol] > 1)
						{
							int totalSpan = pixelSpan(m_rowCols[rowCol],
								arc.SheetPos, (arc.SheetPos + cell.SheetRect.Size[rowCol] -1),
								m_defaultCellSize[rowCol], (RowColType)rowCol);

							int cellSize = cell.MinSize[rowCol];
							if(cellSize > totalSpan)
							{
								int extraSpace = cellSize - totalSpan;
								//Try and add the extraSpace needed to the expandable
								//rowCols in the range of the element.  If there were
								//no expandable rowCols, then the extra space needs
								//to be added to the first rowCol in the range of the
								//element.
								if(!addSpaceToExpandable(m_rowCols[rowCol], extraSpace,
									arc.SheetPos, (arc.SheetPos + cell.SheetRect.Size[rowCol] -1)))
									arc.RowColSize += extraSpace;
							}
						}

			//Next, calculate row and column sizes based on the expandable attribute.
			if(!forMinSize)
				for(int rowCol = 0; rowCol <= 1; ++rowCol)
				{
					int numExpandables = numberOfExpandable(m_rowCols[rowCol]);
					if(numExpandables > 0)
					{
						RowCol arc = (RowCol)m_rowCols[rowCol][m_rowCols[rowCol].Count -1];
						int minSize = pixelSpan(m_rowCols[rowCol], 1, arc.SheetPos, m_defaultCellSize[rowCol], (RowColType)rowCol);
						int extraSpace = 0;
						int areaSize = area.Size[rowCol];
						if(areaSize > minSize)
						{
							extraSpace = areaSize - minSize;
							int extraPer = extraSpace/numExpandables;
							foreach(RowCol arc2 in m_rowCols[rowCol])
								if(arc2.Expandable)
									arc2.RowColSize += extraPer;
						}
					}
				}
		}

		protected void calculateCellAreas(Rect area)
		{
			for(int rowCol = 0; rowCol <= 1; ++rowCol)
			{
				int lastCellPosition = 0;
				int currentPixPosition = area.Position[rowCol];

				CellRegion currentRegion = null;
				int currentRegPixPosition = 0;

				foreach(RowCol arc in m_rowCols[rowCol])
				{
					NumericPair margins = getMargins(arc, (RowColType)rowCol);
					//Calcualte the rowPosition for the components.
					int defaultExtraSpace = (arc.SheetPos - (lastCellPosition +1)) * m_defaultCellSize[rowCol];
					int position = ((currentRegion == null)?currentPixPosition:currentRegPixPosition) + defaultExtraSpace;

					
					foreach(Cell cell in arc.Elements)
					{
						CellRegion reg = this.getRegionForCell(cell);
						if(reg != currentRegion)
						{
							currentRegion = reg;
							currentRegPixPosition = currentRegion.Origin[rowCol];
							position = currentRegPixPosition + defaultExtraSpace;
						}
						cell.CellRect.Position[rowCol] = position + margins.Num1;

						if(cell.SheetRect.Size[rowCol] == 1)
							cell.CellRect.Size[rowCol] = arc.RowColSize;// (margins.Num2 + margins.Num1);
						else
						{
							cell.CellRect.Size[rowCol] = pixelSpan(m_rowCols[rowCol], 
									arc.SheetPos,
									(arc.SheetPos + cell.SheetRect.Size[rowCol] -1),
									m_defaultCellSize[rowCol], (RowColType)rowCol);

							//NOTE that the following line is flawed in that does not calculate the ending rowcol for the cell
							//to get the second margin (bottom/right), it just uses the current rowcol's second margin.
							cell.CellRect.Size[rowCol] -= (margins.Num2 + margins.Num1);
						}
					}

					//Add this rows position and size into the calculation variables.
					lastCellPosition = arc.SheetPos;
					int sizeToAdd = defaultExtraSpace + arc.RowColSize + margins.Num1 + margins.Num2;
					currentPixPosition += sizeToAdd;
					currentRegPixPosition += sizeToAdd;
				}
			}
		}

		int pixelSpan(RowColType type, int startPos, int endPos)
		{
			int lastCellPosition = startPos -1;
			int defaultSpace = 0;
			int mcTotalSize = 0;
			bool lastCellInSpanFound = false;
			IList rcList = m_rowCols[(int)type];

			foreach(RowCol arc in rcList)
			{
				if(arc.SheetPos >= startPos && arc.SheetPos <= endPos)
				{
					NumericPair margins = getMargins(arc, RowColType.rowType);
					defaultSpace = (arc.SheetPos - (lastCellPosition +1)) * m_defaultCellSize[(int)type];
					mcTotalSize += arc.RowColSize + defaultSpace + margins.Num1 + margins.Num2;
					if(arc.SheetPos == endPos)
						lastCellInSpanFound = true;
					lastCellPosition = arc.SheetPos;
				}
				else if (arc.SheetPos > endPos)
					break;
			}
			if(!lastCellInSpanFound)
				mcTotalSize += (endPos - (lastCellPosition)) * m_defaultCellSize[(int)type];

			return mcTotalSize;
		}

		protected int pixelSpan(IList rclist, int startPos, int endPos, int defaultSize, RowColType rowCol)
		{
			int lastCellPosition = startPos -1;
			int mcTotalSize = 0;
			int defaultSpace = 0;
			bool lastCellInSpanFound = false;

			foreach(RowCol arc in rclist)
			{
				if(arc.SheetPos >= startPos && arc.SheetPos <= endPos)
				{
					NumericPair margins = getMargins(arc, rowCol);
					defaultSpace = (arc.SheetPos - (lastCellPosition +1)) * defaultSize;
					mcTotalSize += arc.RowColSize + defaultSpace + margins.Num1 + margins.Num2;
					if(arc.SheetPos == endPos)
						lastCellInSpanFound = true;
					lastCellPosition = arc.SheetPos;
				}
				else if (arc.SheetPos > endPos)
					break;
			}
			if(!lastCellInSpanFound)
				mcTotalSize += (endPos - (lastCellPosition)) * defaultSize;

			return mcTotalSize;
		}

		protected int numberOfExpandable(IList list)
		{
			return numberOfExpandable(list, true, 0,0);
		}

		protected int numberOfExpandable(IList rclist, bool skipRange, int rangeStart, int rangeEnd)
		{
			int numberOfThem = 0;
			foreach(RowCol arc in rclist)
			{
				if(skipRange)
				{
					if(arc.Expandable && (arc.SheetPos < rangeStart ||  arc.SheetPos > rangeEnd))
						numberOfThem++;
				}
				else if(arc.Expandable && (arc.SheetPos >= rangeStart && arc.SheetPos <= rangeEnd))
					numberOfThem++;
			}
			return numberOfThem;
		}

		bool addSpaceToExpandable(IList rclist, int space, int rangeStart, int rangeEnd)
		{
			bool ableToAddSpace = false;
			int numExpandable = numberOfExpandable(rclist, false, rangeStart, rangeEnd);
			if(numExpandable > 0)
			{
				ableToAddSpace = true;
				int addSpace = space/numExpandable;
				foreach(RowCol arc in rclist)
				{
					if(arc.Expandable && (arc.SheetPos >= rangeStart && arc.SheetPos <= rangeEnd))
						arc.RowColSize += addSpace;
				}
			}

			return ableToAddSpace;
		}

		private RowCol defaultRowColFactory(int pos)
		{
			return new RowCol(pos);
		}
	}

	public abstract class Cell
	{
		protected Rect m_sheetRect;
		protected Rect m_cellRect;

		public Cell(Rect sheetRect)
		{
			m_sheetRect = sheetRect;
			m_cellRect = new Rect();
		}


		public Rect SheetRect
		{
			get{return m_sheetRect;}
		}

		public Rect CellRect
		{
			get{return m_cellRect;}
		}
		
		public abstract NumericPair MinSize {get;}
		public abstract NumericPair MaxSize {get;}
	}

	public class RowCol
	{
		protected int m_sheetPos;
		protected int m_size;
		protected int m_minSize;
		protected int m_maxSize;
		protected bool m_expandable;
		protected Size m_margins;
		protected IList m_elements;

		public RowCol(int pos)
		{
			m_sheetPos = pos;
			m_elements = new ArrayList();
		}

		public int SheetPos {get{return m_sheetPos;}}
		public IList Elements {get{return m_elements;}}
		public int RowColSize 
		{
			get{return m_size;}
			set{m_size = value;}
		}

		public int MinSize 
		{
			get{return m_minSize;}
			set{m_minSize = value;}
		}

		public int MaxSize 
		{
			get{return m_maxSize;}
			set{m_maxSize = value;}
		}

		public bool Expandable 
		{
			get{return m_expandable;}
			set{m_expandable = value;}
		}

		public Size Margins
		{
			get{return m_margins;}
			set{m_margins = value;}
		}

		protected internal void addCell(Cell cell)
		{
			m_elements.Add(cell);
		}

		protected internal void removeCell(Cell cell)
		{
			m_elements.Remove(cell);
		}

		protected internal void resetSize(int defaultSize)
		{
			m_size = (m_minSize > 0) ? m_minSize : defaultSize;
		}

	}

	/// <summary>
	/// Defines a region of spreadsheet cells that can have a new origin.
	/// </summary>
	/// <remarks>This class helps to support the concept of cells being sized based on each other, but
	/// having groups of cells located in different coordinate spaces such that their location can be
	/// indipendant of their sibling cells.</remarks>
	public class CellRegion
	{
		protected string m_id;
		protected Rect m_cellArea;
		protected NumericPair m_origin;

		public CellRegion(string id, Rect cellArea, NumericPair origin)
		{
			m_id = id;
			m_cellArea = cellArea;
			m_origin = origin;
		}

		public string Id {get{return m_id;}}
		public Rect CellArea {get{return m_cellArea;}}
		public NumericPair Origin {get{return m_origin;}}
	}
}
